package com.lwq.fast.log.fastlogserver.es.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.lwq.fast.log.fastlogcore.constant.Constants;
import com.lwq.fast.log.fastlogcore.entity.AppEnvIndex;
import com.lwq.fast.log.fastlogcore.entity.AppNameIndex;
import com.lwq.fast.log.fastlogcore.entity.Message;
import com.lwq.fast.log.fastlogserver.common.PageInfo;
import com.lwq.fast.log.fastlogserver.common.util.AppNameEnvCacheUtil;
import com.lwq.fast.log.fastlogserver.es.ElasticSearchUtils;
import com.lwq.fast.log.fastlogserver.es.request.QueryRequest;
import com.lwq.fast.log.fastlogserver.es.response.LogInfoResponse;
import com.lwq.fast.log.fastlogserver.es.service.ElasticSearchService;
import com.lwq.fast.log.fastlogserver.properties.ConfigPropertiesBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author 刘文强
 */
@Service
@Slf4j
public class ElasticSearchServiceImpl implements ElasticSearchService {



    private static final String KEY_WORD = "keyword";
    @Autowired
    private ConfigPropertiesBean configPropertiesBean;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private ElasticSearchUtils elasticSearchUtils;

    /**
     * 将日志信息写入 ES
     *
     * @param logMessage
     */
    @Override
    public void logMessage2Es(String logMessage) throws IOException {
        if (StrUtil.isBlank(logMessage)) {
            return;
        }
        Message message = JSON.parseObject(logMessage, Message.class);
        if (ObjectUtil.isNull(message)) {
            return;
        }
        String index = message.getAppName() ;

        // 创建 appName 索引
        handlerAppName(message.getAppName());

        // 创建 环境信息索引
        handlerEnv(message.getEnv(),message.getEnvDesc());

        // 创建日志索引写入数据
        boolean existIndex = existIndex(index);
        if (!existIndex){
            boolean createSuccess = createLogIndex(index);
            if (createSuccess){
                AppNameEnvCacheUtil.setIndicesCache(index, index);
            }
        }else {
            // 存在，放入缓存
            AppNameEnvCacheUtil.setIndicesCache(index,index);
        }
        addLog2Index(index,logMessage);
    }


    /**
     * 检查索引是否存在 存在返回true,否则返回false
     *
     * @param index 索引名称
     * @return boolean
     */
    @Override
    public boolean existIndex(String index) throws IOException {

        String indicesCacheByKey = AppNameEnvCacheUtil.getIndicesCacheByKey(index);
        if (StrUtil.isNotBlank(indicesCacheByKey)){
            //说明已经存在索引
            return true;
        }
        GetIndexRequest request = new GetIndexRequest(index);
        return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }


    /**
     * 创建日志索引
     *
     * @param index 索引名称
     * @return
     */
    @Override
    public boolean createLogIndex(String index) throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(index);

        //索引setting
        request.settings(Settings.builder()
                .put("index.number_of_shards", configPropertiesBean.getShards())
                .put("index.number_of_replicas", configPropertiesBean.getReplicas()));


        //索引mapping
        XContentBuilder builder = XContentFactory.jsonBuilder();
        builder.startObject();
        builder.field("dynamic",true);
        {
            builder.startObject("properties");
            {
                builder.startObject("appName");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("env");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("threadName");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("level");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("traceId");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("className");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("content");
                {
                    builder.field("type", "text");
                }
                builder.endObject();
                builder.startObject("dateTime");
                {
                    builder.field("type", "date");
                }
                builder.endObject();

            }
            builder.endObject();
        }
        builder.endObject();
        request.mapping(builder);

        //设置别名
        //request.alias(new Alias(index));

        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        return createIndexResponse.isAcknowledged();

    }

    /**
     * 添加文档信息到索引中
     *
     * @param index 索引名称
     * @param jsonDocument 文档信息-json格式
     * @return
     */
    @Override
    public int addLog2Index(String index, String jsonDocument) throws IOException {
        IndexRequest request = new IndexRequest(index);
        request.id(IdUtil.simpleUUID());
        request.source(jsonDocument, XContentType.JSON);
        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        return response.status().getStatus();

    }

    /**
     * 获取所有应用名称
     *
     * @return {@code List<String>}
     */
    @Override
    public List<String> getAllAppNames() throws IOException {

        SearchRequest request = new SearchRequest(Constants.APP_NAME_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        request.source(searchSourceBuilder);
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        if (ObjectUtil.isNull(search)){
            return null;
        }
        SearchHits hits = search.getHits();
        if (ObjectUtil.isNull(hits)){
            return null;
        }
        List<String> result = new ArrayList<>();
        hits.forEach(entity ->{
            Map<String, Object> sourceAsMap = entity.getSourceAsMap();
            if (CollectionUtil.isNotEmpty(sourceAsMap)){
                String appName = (String) sourceAsMap.get("appName");
                if (ObjectUtil.isNotNull(appName)){
                    result.add(appName);
                }
            }
        });
        return result;
    }

    /**
     * 获取全部环境信息
     *
     * @return {@code List<AppEnvIndex>}
     */
    @Override
    public List<AppEnvIndex> getAllEnv() throws IOException {

        SearchRequest request = new SearchRequest(Constants.APP_ENV_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        request.source(searchSourceBuilder);
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        if (ObjectUtil.isNull(search)){
            return null;
        }
        SearchHits hits = search.getHits();
        if (ObjectUtil.isNull(hits)){
            return null;
        }
        List<AppEnvIndex> result = new ArrayList<>();
        hits.forEach(entity ->{
            Map<String, Object> sourceAsMap = entity.getSourceAsMap();
            if (CollectionUtil.isNotEmpty(sourceAsMap)){
                String env = (String) sourceAsMap.get("env");
                String envDesc = (String) sourceAsMap.get("envDesc");
                AppEnvIndex appEnvIndex = new AppEnvIndex();
                appEnvIndex.setEnv(env);
                appEnvIndex.setEnvDesc(envDesc);
                result.add(appEnvIndex);
            }
        });
        return result;
    }


    /**
     * 根据条件查询数据
     *
     * @param request 请求参数
     * @return PageInfo
     */
    @Override
    public PageInfo queryData(QueryRequest request) throws IOException {
        if (CollectionUtil.isEmpty(request.getAppNameList()) || StrUtil.isBlank(request.getEnv()) || ObjectUtil.isNull(request.getStartTime()) || ObjectUtil.isNull(request.getEndTime())){
            return null;
        }
        List<String> existIndex = getExistIndex(request);
        if (CollectionUtil.isEmpty(existIndex)){
            return null;
        }
        request.setAppNameList(existIndex);

        String[] indexs = ArrayUtil.toArray(request.getAppNameList(), String.class);
        SearchRequest searchRequest = new SearchRequest(indexs);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        elasticSearchUtils.buildMatchPhrase("level", request.getLevel()).ifPresent((builder) -> queryBuilder.must(builder));
        elasticSearchUtils.buildMatchPhrase("traceId", request.getTraceId()).ifPresent((builder) -> queryBuilder.must(builder));
        elasticSearchUtils.buildQueryString("content", request.getContent()).ifPresent((builder -> queryBuilder.must(builder)));
        elasticSearchUtils.buildMatchPhrase("env", request.getEnv()).ifPresent((builder -> queryBuilder.must(builder)));
        RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("dateTime");
        rangeQueryBuilder.gte(request.getStartTime());
        rangeQueryBuilder.lte(request.getEndTime());
        queryBuilder.must(rangeQueryBuilder);
        searchSourceBuilder.query(queryBuilder);

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("content");
        highlightBuilder.preTags("<span style=\"color:red\">");
        highlightBuilder.postTags("</span>");
        highlightBuilder.numOfFragments(0);
        searchSourceBuilder.highlighter(highlightBuilder);
        int from = (request.getPageNum() - 1) * request.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(request.getPageSize());

        ScoreSortBuilder scoreSortBuilder = SortBuilders.scoreSort().order(SortOrder.DESC);
        FieldSortBuilder dateTimeSortBuilder = SortBuilders.fieldSort("dateTime").order(SortOrder.DESC);
        searchSourceBuilder.sort(dateTimeSortBuilder).sort(scoreSortBuilder);
        //searchSourceBuilder.sort("dateTime", SortOrder.DESC);


        searchRequest.source(searchSourceBuilder);
        log.info("searchSourceBuilder={}",searchSourceBuilder);
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = search.getHits();
        if (ObjectUtil.isNull(hits)){
            return null;
        }
        TotalHits totalHits = hits.getTotalHits();
        if (ObjectUtil.isNull(totalHits) || totalHits.value <= 0){
            return null;
        }
        int total = (int) totalHits.value;

        List<LogInfoResponse> rows = new ArrayList<>();
        hits.forEach(hit ->{
            LogInfoResponse logInfoResponse = new LogInfoResponse();
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            logInfoResponse.setAppName(ObjectUtil.isNull(sourceAsMap.get("appName")) ? null : sourceAsMap.get("appName").toString());
            logInfoResponse.setLevel(ObjectUtil.isNull(sourceAsMap.get("level"))? null : sourceAsMap.get("level").toString());
            logInfoResponse.setThreadName(ObjectUtil.isNotNull(sourceAsMap.get("threadName")) ? sourceAsMap.get("threadName").toString(): null);
            logInfoResponse.setTraceId(ObjectUtil.isNull(sourceAsMap.get("traceId"))? null : sourceAsMap.get("traceId").toString());
            logInfoResponse.setClassName(ObjectUtil.isNull(sourceAsMap.get("className"))? null : sourceAsMap.get("className").toString());
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (CollectionUtil.isNotEmpty(highlightFields)){
                HighlightField content = highlightFields.get("content");
                if (ObjectUtil.isNotNull(content)){
                    Text[] fragments = content.getFragments();
                    StrBuilder strBuilder = StrBuilder.create("");
                    for (Text fragment : fragments) {
                        strBuilder.append(fragment.toString());
                    }
                    logInfoResponse.setContent(strBuilder.toString());
                }else{
                    logInfoResponse.setContent(ObjectUtil.isNull(sourceAsMap.get("content"))? null : sourceAsMap.get("content").toString());
                }
            }else {
                logInfoResponse.setContent(ObjectUtil.isNull(sourceAsMap.get("content"))? null : sourceAsMap.get("content").toString());
            }

            logInfoResponse.setDateTime(ObjectUtil.isNull(sourceAsMap.get("dateTime"))? null : new Date((long)sourceAsMap.get("dateTime")));
            rows.add(logInfoResponse);
        });
        PageInfo pageInfo = new PageInfo();
        pageInfo.setRows(rows);
        pageInfo.setPageNum(request.getPageNum());
        pageInfo.setPageSize(request.getPageSize());
        pageInfo.setTotal(total);
        return pageInfo;
    }


    /**
     * 获取全部索引名称
     *
     * @return
     */
    @Override
    public List<String> getAllIndices() throws IOException {
        GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
        GetAliasesResponse alias = restHighLevelClient.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);
        if (ObjectUtil.isNull(alias)){
            return Collections.EMPTY_LIST;
        }
        Map<String, Set<AliasMetaData>> aliasesMap = alias.getAliases();
        if (CollectionUtil.isEmpty(aliasesMap)){
            return Collections.EMPTY_LIST;
        }
        Set<String> indices = aliasesMap.keySet();
        return CollectionUtil.newArrayList(indices);
    }

    /**
     * 获取存在的索引
     *
     * @param request
     * @return
     */
    private List<String> getExistIndex(QueryRequest request) {
        List<String> existIndex = new ArrayList<>();
        request.getAppNameList().forEach(index ->{
            try {
                if (existIndex(index)){
                    existIndex.add(index);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return existIndex;
    }





    /**
     * 处理环境信息
     *
     * @param env
     * @param envDesc
     */
    private void handlerEnv(String env, String envDesc) throws IOException {
        String envCacheByKey = AppNameEnvCacheUtil.getEnvCacheByKey(env);
        if (StrUtil.isNotBlank(envCacheByKey)){
            // 说明已经存在环境信息
            return;
        }
        // 检查文档中 env 是否已经存在，如果不存在，则新增文档
        GetRequest request = new GetRequest(Constants.APP_ENV_INDEX,env);
        // 禁止获取_source
        request.fetchSourceContext(new FetchSourceContext(false))
                // 禁用获取存储字段
                .storedFields("_none_");
        boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
        if (!exists){
            // 若不存在则 新增文档 。appName 作为id
            IndexRequest indexRequest = new IndexRequest(Constants.APP_ENV_INDEX);
            //环境信息作为id
            indexRequest.id(env);
            AppEnvIndex appEnvIndex = AppEnvIndex.builder()
                    .env(env)
                    .envDesc(StrUtil.isBlank(envDesc) ? env : StrUtil.sub(envDesc, 0, 15))
                    .build();
            indexRequest.source(JSON.toJSONString(appEnvIndex),XContentType.JSON);
            restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
        }
        AppNameEnvCacheUtil.setEnvCache(env, env);
    }

    /**
     * 处理appName 索引
     *
     * @param appName
     */
    private void handlerAppName(String appName) throws IOException {
        String appNameCacheByKey = AppNameEnvCacheUtil.getAppNameCacheByKey(appName);
        if (StrUtil.isNotBlank(appNameCacheByKey)){
            // 说明应用名称已经存在
            return;
        }
        // 检查文档 appName是否已经存在，如果不存在，则新增
        GetRequest request = new GetRequest(Constants.APP_NAME_INDEX, appName);
        // 禁止获取_source
        request.fetchSourceContext(new FetchSourceContext(false))
            // 禁用获取存储字段
            .storedFields("_none_");
        boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
        if (!exists){
            // 若不存在则 新增文档 。appName 作为id
            IndexRequest indexRequest = new IndexRequest(Constants.APP_NAME_INDEX);
            indexRequest.id(appName);
            indexRequest.source(JSON.toJSONString(AppNameIndex.builder().appName(appName).build()),XContentType.JSON);
            restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
        }
        AppNameEnvCacheUtil.setAppNameCache(appName, appName);
    }
}
